﻿namespace Trader.Indicators.TechnicalAnalysis
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using BO.Interface;
    using Interface;

    ///<seealso cref="http://www.chartpatterns.com/flagsandpennants.htm"/>
    public class FlagSignal : ISignal
    {
        #region Private Member's

        ///<summary>
        ///Number of days to check before the buy date</summary>
        private readonly int days;

        public int TestDays
        {
            get { return days; }
        }

        public Guid ID
        {
            get { return new Guid("8A5BD971-6E10-4a9a-B39A-14910F869DDB"); }
        }

        public string Name
        {
            get { return string.Format("Flag ({0})", days); }
        }

        public Uri URL
        {
            get { return new Uri("http://www.chartpatterns.com/flagsandpennants.htm"); }
        }

        #endregion

        #region Cons'

        ///<summary>
        ///FlagSignal constructor: calculates the days and deviation</summary>
        public FlagSignal(int days)
        {
            this.days = days;
        }

        #endregion

        #region ISignal Members

        ///<summary>
        ///Generate a recomendation for the stock at the spesific time
        ///</summary>
        ///<param name="stock"> The stock to buy</param>
        ///<param name="buyTime"> The date for buying</param>
        ///<returns>Yes or No</returns>
        public Recommendation GenerateRecommendation(IStock stock, DateTime buyTime)
        {
            if (!stock.Quotes.ContainsKey(buyTime)) //Check if buy point has quote
            {
                return Recommendation.NONE;
            }

            IList<decimal> highPrices = GetHighPrices(stock, buyTime);
            IList<decimal> lowPrices = GetLowPrices(stock, buyTime);

            if (!highPrices.Any()) //No quotes during period
            {
                return Recommendation.NONE;
            }
            if (!lowPrices.Any()) //No quotes during period
            {
                return Recommendation.NONE;
            }

            decimal highValue = highPrices[0];
            decimal lowValue = lowPrices[0];

            int numOfValus = highPrices.Count;

            int maxIndex = 0;
            int minIndex = 0;
            int i = 0;
            int maxValue = 0;
            int minValue = 0;

            foreach (decimal num in highPrices)
            {
                if (num > maxValue)
                {
                    maxValue = (int) num;
                    maxIndex = i;
                }

                if (num < minValue)
                {
                    minValue = (int) num;
                    minIndex = i;
                }
                i++;
            }

            if (numOfValus < 2)
            {
                return Recommendation.NONE;
            }

            IList<decimal> volumes = GetVolumes(stock, buyTime);

            bool Buy = false;
            bool Short = false;

            // Check for "bull" flag pattern
            for (i = maxIndex; i < numOfValus; ++i)
            {
                if (i == 0)
                {
                    continue;
                }
                if (highPrices[i] > highPrices[i - 1] || lowPrices[i] > lowPrices[i - 1])
                {
                    return Recommendation.NONE;
                }
            }
            // Uptrend
            if (stock.Quotes[buyTime].High > highPrices[numOfValus - 1])
            {
                Buy = true;
            }

            if (!Buy)
            {
                // Check for "berr" flag pattern
                for (i = minIndex; i < numOfValus; ++i)
                {
                    if (i == 0)
                    {
                        continue;
                    }
                    if (highPrices[i] < highPrices[i - 1] || lowPrices[i] < lowPrices[i - 1])
                    {
                        return Recommendation.NONE;
                    }
                }
                // Downtrend
                if (stock.Quotes[buyTime].Low < lowPrices[numOfValus - 1])
                {
                    Short = true;
                }
            }

            // Increase in volume on buy time
            if (stock.Quotes[buyTime].Volume > volumes[numOfValus - 1]*1.2M)
            {
                if (Buy)
                {
                    return Recommendation.BUY;
                }
                else if (Short)
                {
                    return Recommendation.SHORT;
                }
                else
                {
                    return Recommendation.NONE;
                }
            }
            return Recommendation.NONE;
        }

        #endregion

        ///<summary>
        ///Get Period Closings
        ///</summary>
        ///<param name="stock"> The stock to buy</param>
        ///<param name="buyTime"> The date for buying</param>
        ///<returns></returns>
        private IEnumerable<decimal> GetPeriodClosings(IStock stock, DateTime buyTime)
        {
            var periodClosings = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    periodClosings.Add(quote.Closing);
                }
            }
            return periodClosings;
        }

        ///<summary>
        ///Get all high prices of the stock from the time before the buy date
        ///</summary>
        ///<param name="stock"> The stock</param>
        ///<param name="buyTime"> The buy date</param>
        ///<returns> List of high prices</returns>
        private IList<decimal> GetHighPrices(IStock stock, DateTime buyTime)
        {
            var highPrices = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    highPrices.Add(quote.High);
                }
            }
            return highPrices;
        }

        ///<summary>
        ///Get all low prices of the stock from the time before the buy date
        ///</summary>
        ///<param name="stock"> The stock</param>
        ///<param name="buyTime"> The buy date</param>
        ///<returns> List of low prices</returns>
        private IList<decimal> GetLowPrices(IStock stock, DateTime buyTime)
        {
            var lowPrices = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    lowPrices.Add(quote.Low);
                }
            }
            return lowPrices;
        }

        ///<summary>
        ///Get all stock trade volumes from the time before the buy date
        ///</summary>
        ///<param name="stock"> The stock</param>
        ///<param name="buyTime"> The buy date</param>
        ///<returns> List of trade volumes</returns>
        private IList<decimal> GetVolumes(IStock stock, DateTime buyTime)
        {
            var volume = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    volume.Add(quote.Volume);
                }
            }
            return volume;
        }

        public override string ToString()
        {
            return string.Format("Flag({0})", days);
        }
    }
}